#define lib_flip_c
#define LUA_LIB

extern "C" {
#include "../lua.h"
#include "../lauxlib.h"
#include "../lualib.h"

#include "../lj_def.h" // Included always for the typedefs
#include "../lj_lib.h" // Included always for the typedefs
}

#undef cast
#define GVL_EXCLUDE_STDINT 1 // lj2 has type typedefs

#include "flip/flip_internal.hpp"
#include <gvl/io/fstream.hpp>
#include <flip/loader/png_loader.hpp>
#include "util.hpp"
//#include <flip/gl/gl.hpp>
#include <flip/2d/geom_buffer.hpp>
#include <flip/gl/button_names.hpp>

using namespace Flip;

/* ------------------------------------------------------------------------ */

LUA_CHECK_FUNC(Flip::Image, image, UDTYPE_FLIP_IMAGE)
LUA_CHECK_FUNC(Flip::RenderableImage, sprite, UDTYPE_FLIP_SPRITE)
LUA_CHECK_FUNC(Flip::Font, font, UDTYPE_FLIP_FONT)

// Global flip data
luaflipData globalDataInstance;



extern "C" {

// <Image methods>

#define LJLIB_MODULE_flip_image_method

LJLIB_CF(flip_image_method___gc)
{
	if(Image* image = static_cast<Image*>(try_image(L, 1)))
	{
		// TODO: Clearing the image tag can conceivably be missed by
		// traced calls. We need some other way to signal this.
		lua_clear_object_tag(L, 1);
		image->~Image();
	}

	return 0;
}

LJLIB_CF(flip_image_method_setpixel)
{
	auto& image = check_image(L, 1);

	int x = luaL_checkint(L, 2);
	int y = luaL_checkint(L, 3);
	uint32_t r = luaL_checkint(L, 4);
	uint32_t g = luaL_checkint(L, 5);
	uint32_t b = luaL_checkint(L, 6);
	uint32_t a = luaL_optint(L, 7, 255);
	
	image.setPixel(x, y, image.pixel(r, g, b, a));
	return 0;
}

LJLIB_CF(flip_image_method_dim)
{
	auto& image = check_image(L, 1);

	lua_pushinteger(L, image.width);
	lua_pushinteger(L, image.height);
	return 2;
}

LJLIB_PUSH(top-2) LJLIB_SET(!) // Sprite metatable

LJLIB_CF(flip_image_method_to_sprite)
{
	auto& image = check_image(L, 1);

	void* mem = lua_create_object(L, UDTYPE_FLIP_SPRITE, sizeof(RenderableImage));
	RenderableImage* self = new (mem) RenderableImage(image.data());
	self->bevel(RenderableImage::Repeat);

	return 1;
}

LJLIB_PUSH(top-1) LJLIB_SET(__index)

// <Sprite methods>

#define LJLIB_MODULE_flip_sprite_method

LJLIB_CF(flip_sprite_method___gc)
{
	if(RenderableImage* sprite = static_cast<RenderableImage*>(try_sprite(L, 1)))
	{
		// TODO: Clearing the image tag can conceivably be missed by
		// traced calls. We need some other way to signal this.
		lua_clear_object_tag(L, 1);
		sprite->~RenderableImage();
	}

	return 0;
}

// <Font methods>

#define LJLIB_MODULE_flip_font_method

LJLIB_CF(flip_font_method___gc)
{
	if(Font* font = static_cast<Font*>(try_font(L, 1)))
	{
		// TODO: Clearing the image tag can conceivably be missed by
		// traced calls. We need some other way to signal this.
		lua_clear_object_tag(L, 1);
		font->~Font();
	}

	return 0;
}

#define LJLIB_MODULE_flip

// Global

LJLIB_CF(flip_video)
{
	luaflipData& data = globalData(L);

	unsigned width = luaL_checkint(L, 1);
	unsigned height = luaL_checkint(L, 2);
	
	data.currentWindow.setMode(width, height, false);
	data.currentWindow.visible(true);
	return 0;
}

LJLIB_CF(flip_update)
{
	luaflipData& data = globalData(L);
	bool ret = data.currentWindow.update();
	lua_pushboolean(L, ret);
	return 1;
}

LJLIB_CF(flip_end_drawing)
{
	luaflipData& data = globalData(L);
	data.drawBuffer.flush();
	data.currentWindow.endDrawing();
	return 0;
}

LJLIB_CF(flip_throttle_fps)
{
	luaflipData& data = globalData(L);
	int fps = luaL_checkint(L, 1);
	
	data.currentWindow.throttleFps(fps);
	return 0;
}

LJLIB_CF(flip_next_event)
{
	luaflipData& data = globalData(L);

	if(data.currentWindow.events.empty())
		return 0;

	Flip::Event ev = data.currentWindow.events.front();
	data.currentWindow.events.pop_front();

	switch(ev.type)
	{
		case Flip::Event::Button:
		{
			lua_pushstring(L, "button");
			
			lua_createtable(L, 0, 3);
			
			lua_pushinteger(L, ev.button.id);
			lua_setfield(L, -2, "id");

			lua_pushboolean(L, ev.button.state);
			lua_setfield(L, -2, "state");
			
			lua_pushinteger(L, ev.button.character);
			lua_setfield(L, -2, "character");
			break;
		}

		default:
			return 0;
	}

	return 2;
}

LJLIB_CF(flip_close)
{
	luaflipData& data = globalData(L);
	data.currentWindow.close();
	return 0;
}

LJLIB_CF(flip_fsaa)
{
	luaflipData& data = globalData(L);

	int level = luaL_checkint(L, 1);
	data.currentWindow.fsaa(level);
	return 0;
}

LJLIB_CF(flip_mouse_pos)
{
	luaflipData& data = globalData(L);

	auto pos = data.currentWindow.mousePos();
	lua_pushinteger(L, pos.x);
	lua_pushinteger(L, pos.y);
	return 2;
}

LJLIB_CF(flip_reset_transform)
{
	luaflipData& data = globalData(L);
	data.drawBuffer.flush();
	data.currentWindow.resetTransform();
	data.currentWindow.updateTransform();
	return 0;
}

LJLIB_CF(flip_zoom)
{
	luaflipData& data = globalData(L);
	double x = lj_lib_checknum(L, 1);
	double y = lj_lib_checknum(L, 2);
	double scale = lj_lib_checknum(L, 3);

	gvl::fvec2 offset((float(x)), float(y));
	
	data.drawBuffer.flush();
	gvl::fmat2x2& transform = data.currentWindow.transform();
	gvl::fvec2& translation = data.currentWindow.translation();

	translation = transform.invert() * translation;
	translation -= offset;
	transform *= scale;
	translation = transform * translation;

	data.currentWindow.updateTransform();

	return 0;
}

LJLIB_CF(flip_begin_quads)
{
	luaflipData& data = globalData(L);

	data.drawBuffer.geomQuads();
	return 0;
}

LJLIB_CF(flip_quad)
{
	luaflipData& data = globalData(L);
	double v[8];

	v[0] = luaL_checknumber(L, 1);
	v[1] = luaL_checknumber(L, 2);
	v[2] = luaL_checknumber(L, 3);
	v[3] = luaL_checknumber(L, 4);
	v[4] = luaL_checknumber(L, 5);
	v[5] = luaL_checknumber(L, 6);
	v[6] = luaL_checknumber(L, 7);
	v[7] = luaL_checknumber(L, 8);

	data.drawBuffer.quad(
		(float)v[0], (float)v[1], (float)v[2], (float)v[3],
		(float)v[4], (float)v[5], (float)v[6], (float)v[7]);
	
	return 0;
}

LJLIB_CF(flip_begin_sprites)
{
	luaflipData& data = globalData(L);

	data.drawBuffer.geomImages();
	return 0;
}

LJLIB_CF(flip_begin_lines)
{
	luaflipData& data = globalData(L);

	data.drawBuffer.geomLines();
	return 0;
}

LJLIB_CF(flip_begin_triangle_fan)
{
	luaflipData& data = globalData(L);

	data.drawBuffer.geomTriFan();
	return 0;
}

LJLIB_CF(flip_begin_line_strip)
{
	luaflipData& data = globalData(L);

	data.drawBuffer.geomLineStrip();
	return 0;
}

LJLIB_CF(flip_begin_points)
{
	luaflipData& data = globalData(L);

	data.drawBuffer.geomPoint();
	return 0;
}

LJ_FUNCA void LJ_FASTCALL lj_flip_sprite(void* sprite, lua_Number x, lua_Number y)
{
	luaflipData& data = globalDataInstance;
	data.drawBuffer.image(float(x), float(y), *static_cast<RenderableImage*>(sprite)->data());
}

LJ_FUNCA void LJ_FASTCALL lj_flip_sprite_centered(void* sprite, lua_Number x, lua_Number y)
{
	luaflipData& data = globalDataInstance;
	data.drawBuffer.imageCentered(float(x), float(y), *static_cast<RenderableImage*>(sprite)->data());
}

LJ_FUNCA void LJ_FASTCALL lj_flip_sprite_centered_rot(void* sprite, lua_Number x, lua_Number y, lua_Number ang)
{
	luaflipData& data = globalDataInstance;
	data.drawBuffer.imageCenteredRot(float(x), float(y), float(ang), 1.f, *static_cast<RenderableImage*>(sprite)->data());
}

LJ_FUNCA void LJ_FASTCALL lj_flip_line(lua_Number x1, lua_Number y1, lua_Number x2, lua_Number y2)
{
	luaflipData& data = globalDataInstance;
	data.drawBuffer.line(float(x1), float(y1), float(x2), float(y2));
}

LJ_FUNCA void LJ_FASTCALL lj_flip_vertex(lua_Number x1, lua_Number y1)
{
	luaflipData& data = globalDataInstance;
	data.drawBuffer.vertex(float(x1), float(y1));
}


LJLIB_CF(flip_sprite)  LJLIB_REC(flip_sprite_dd IRCALL_lj_flip_sprite)
{
	auto& sprite = check_sprite(L, 1);

	double x = luaL_checknumber(L, 2);
	double y = luaL_checknumber(L, 3);

	lj_flip_sprite(&sprite, x, y);
	
	return 0;
}

LJLIB_CF(flip_sprite_centered)  LJLIB_REC(flip_sprite_dd IRCALL_lj_flip_sprite_centered)
{
	auto& sprite = check_sprite(L, 1);

	double x = luaL_checknumber(L, 2);
	double y = luaL_checknumber(L, 3);

	lj_flip_sprite_centered(&sprite, x, y);
	
	return 0;
}

LJLIB_CF(flip_sprite_centered_rot)  LJLIB_REC(flip_sprite_dddd IRCALL_lj_flip_sprite_centered_rot)
{
	luaflipData& data = globalDataInstance;
	auto& sprite = check_sprite(L, 1);

	double x = lj_lib_checknum(L, 2);
	double y = lj_lib_checknum(L, 3);
	double ang = lj_lib_checknum(L, 4);
	double scale = lj_lib_optnum(L, 5, 1.0);

	data.drawBuffer.imageCenteredRot(float(x), float(y), float(ang), float(scale), *static_cast<RenderableImage&>(sprite).data());

	return 0;
}

LJLIB_CF(flip_line)  LJLIB_REC(flip_line IRCALL_lj_flip_line)
{
	double x1 = luaL_checknumber(L, 1);
	double y1 = luaL_checknumber(L, 2);
	double x2 = luaL_checknumber(L, 3);
	double y2 = luaL_checknumber(L, 4);

	lj_flip_line(x1, y1, x2, y2);
	
	return 0;
}

LJLIB_CF(flip_vertex)  LJLIB_REC(flip_vertex IRCALL_lj_flip_vertex)
{
	double x1 = luaL_checknumber(L, 1);
	double y1 = luaL_checknumber(L, 2);

	lj_flip_vertex(x1, y1);
	
	return 0;
}

LJLIB_CF(flip_clear)
{
	luaflipData& data = globalData(L);

	data.currentWindow.clear();
	return 0;
}

LJLIB_CF(flip_clear_color)
{
	luaflipData& data = globalData(L);

	double r = lj_lib_checknum(L, 1);
	double g = lj_lib_checknum(L, 2);
	double b = lj_lib_checknum(L, 3);

	data.currentWindow.clearColor((float)r, (float)g, (float)b);
	return 0;
}


LJLIB_CF(flip_color)
{
	luaflipData& data = globalData(L);
	double r = lj_lib_checknum(L, 1);
	double g = lj_lib_checknum(L, 2);
	double b = lj_lib_checknum(L, 3);
	
	data.drawBuffer.color((float)r, (float)g, (float)b);
	return 0;
}

LJLIB_CF(flip_draw_sprite)
{
	luaflipData& data = globalData(L);
	auto& sprite = check_sprite(L, 1);

	float x = float(luaL_checknumber(L, 2));
	float y = float(luaL_checknumber(L, 3));

	data.drawBuffer.geomQuads();
	data.drawBuffer.image(x, y, *sprite.data());
	return 0;
}

// <Images>

LJLIB_PUSH(top-2) LJLIB_SET(!) // Image metatable

LJLIB_CF(flip_create_image)
{
	int width = luaL_checkint(L, 1);
	int height = luaL_checkint(L, 2);

	void* mem = lua_create_object(L, UDTYPE_FLIP_IMAGE, sizeof(Image));
	Image* self = new (mem) Image(width, height, Flip::pf_8888);

	return 1;
}

LJLIB_CF(flip_load_image)
{
	char const* path = luaL_checkstring(L, 1);

	void* mem = lua_create_object(L, UDTYPE_FLIP_IMAGE, sizeof(Image));
	Image* self = new (mem) Image(
		Flip::loadPng(gvl::stream_ptr(new gvl::fstream(path, "rb"))));

	return 1;
}

// <Sprites>



// <Fonts>

LJLIB_PUSH(top-4) LJLIB_SET(!) // Font metatable

LJLIB_CF(flip_load_font)
{
	char const* path = luaL_checkstring(L, 1);
	// TODO: Validate path

	int size = luaL_checkint(L, 2);

	void* mem = lua_create_object(L, UDTYPE_FLIP_FONT, sizeof(Font));
	Font* self = new (mem) Font(path, size);
	
	return 1;
}

LJ_FUNCA void LJ_FASTCALL lj_flip_text(void* font, char const* str, lua_Number x, lua_Number y)
{
	luaflipData& data = globalDataInstance;
	static_cast<Font*>(font)->drawText(data.drawBuffer, str, float(x), float(y));
}

LJLIB_CF(flip_text)  LJLIB_REC(flip_text IRCALL_lj_flip_text)
{
	luaflipData& data = globalData(L);
	auto& font = check_font(L, 1);

	char const* str = luaL_checkstring(L, 2);
	double x = luaL_checknumber(L, 3);
	double y = luaL_checknumber(L, 4);

	lj_flip_text(&font, str, x, y);
	return 0;
}

/* ------------------------------------------------------------------------ */

#include "../lj_libdef.h"
#include "../lj_lib.h"

LUALIB_API int luaopen_flip(lua_State *L)
{
	//copyTV(L, L->top, L->top-1); L->top++;
	//lua_setfield(L, LUA_REGISTRYINDEX, LUA_SERIALIZE_CONTEXT);
	LJ_LIB_REG_(L, NULL, flip_font_method);
	LJ_LIB_REG_(L, NULL, flip_sprite_method);
	LJ_LIB_REG_(L, NULL, flip_image_method);
	LJ_LIB_REG(L, flip);

	for(int i = 0; Flip::buttonNames[i].name; ++i)
	{
		lua_pushinteger(L, Flip::buttonNames[i].button);
		lua_setfield(L, -2, Flip::buttonNames[i].name);
	}

	return 1;
}

} // extern "C"
